package nmp.lib;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class CNmpUdp 
{
	public static final boolean CLIENT = false;
	public static final boolean SERVER = true;
	
	protected static final String BUNDLE_DATA_STRING = "bundle_data_string";
	protected static final String BUNDLE_DATA_ADDRESS = "bundle_data_address";
	protected static final String BUNDLE_DATA_PORT = "bundle_data_port";
	
	protected static final int MSG_STRING = 0;
	protected static final int MSG_TIMEOUT = 1;
	protected static final int MSG_READY = 2;
	
    class CMsgData
    {
    	public String m_address;
    	public int m_port;
    	public String m_data;
        public CMsgData()
        {
        }
        public CMsgData(String address,int port,String data)
        {
        	m_address = address;
        	m_port = port;
        	m_data = data;
        }       
    };
    
	private DatagramSocket m_datagram_socket = null; //for udp
    
	private Thread m_read_thread = null;
    private Thread m_write_thread = null;
    
    //private volatile boolean m_read_thread_run = false;
    private AtomicBoolean m_thread_run = new AtomicBoolean(false);
    
    private Handler m_read_handler = null;
    
    private int m_timeout = 0;
    private boolean m_timeout_msg = false;
    
    //private InetAddress m_remote_address = null;
    private boolean m_isReady = false;
    private byte[] inbuffer = new byte[1024];
    
    private int m_bind_port = 0 ;
    private InetAddress m_bind_address = null;
    
    private int m_remote_port = 0;
    private InetAddress m_remote_address = null;
    
    private BlockingQueue<CMsgData> write_queue;
    

    public CNmpUdp(){	
    }
    
    public CNmpUdp(int port){
    	m_bind_port = port;
    }
    
    public void setTimeout(int timeout, boolean timeout_msg){
    	m_timeout = timeout;   
    	m_timeout_msg = timeout_msg;
    }
    
    public boolean start()
    {
    	write_queue = new ArrayBlockingQueue<CMsgData>(20,true);
    	
        m_read_handler = new Handler() 
        {  
           Bundle data;
           String data_str;
           String data_addr;
           int data_port;
    	   @Override  
    	   public void handleMessage(Message msg) {   
    	      switch (msg.what) {
                case MSG_STRING:
                	data = msg.getData();
                	data_str = data.getString(BUNDLE_DATA_STRING);
                	data_addr = data.getString(BUNDLE_DATA_ADDRESS);
                	data_port = data.getInt(BUNDLE_DATA_PORT);
                	
                	GetString(data_str,data_addr,data_port);
                    break;
                    
                case MSG_READY: 
                	 m_isReady = true;
                	 UpdateStatus(MSG_READY);
                	 break;
                	 
                case MSG_TIMEOUT:	
                	UpdateStatus(MSG_TIMEOUT);
                	break;
                	
                default:
                	UpdateStatus(msg.what);
                	break;
              }    		   
    	      
    	      super.handleMessage(msg);  
    	   }  
        };  
        
        m_read_thread = new Thread()
        { 
        	String packetString ="";
        	String addressString ="";
        	DatagramPacket packet;        	
            @Override
            public void run()
            { 
    			try 
    			{
    				if(m_bind_port != 0)
    					m_datagram_socket = new DatagramSocket(m_bind_port);
    				else
    					m_datagram_socket = new DatagramSocket();
    				
    				m_bind_address = m_datagram_socket.getLocalAddress();
    				m_bind_port = m_datagram_socket.getLocalPort();		    				
    				m_datagram_socket.setSoTimeout(m_timeout);
    				m_datagram_socket.setBroadcast(true);
    				m_datagram_socket.setReuseAddress(true);
				} 
    			catch (IOException e) 
    			{
					e.printStackTrace();
				}	
    				
    			NoticeStatus(MSG_READY);	
	            
            	while(m_thread_run.get())
            	{               		  
	                try
	                {                		
                		packet = new DatagramPacket(inbuffer,inbuffer.length);
                		m_datagram_socket.setSoTimeout(m_timeout);            		
    		            m_datagram_socket.receive(packet);
    		            
    		            packetString = stringFromPacket(packet);	
    		            m_remote_address = packet.getAddress();
    		            m_remote_port = packet.getPort();     
    		            
	                	addressString = m_remote_address.getHostAddress();
	    	            Message msg = new Message();
	    	            Bundle data = new Bundle();	 
	    	            Log.e(this.toString(),"udp get message" + packetString);
	    	            data.putString(BUNDLE_DATA_STRING,packetString);
	    	            data.putString(BUNDLE_DATA_ADDRESS,addressString);
	    	            data.putInt(BUNDLE_DATA_PORT,m_remote_port);
	    	            
	    	            msg.what = MSG_STRING;
	    	            msg.setData(data);
	    	            m_read_handler.sendMessage(msg);    
	                }
	    	        catch (InterruptedIOException e) 
	    	        {
    	        		Log.e(this.toString(),"client get message timeout!!!");
    	        		if(m_timeout_msg){
    	        			NoticeStatus(MSG_TIMEOUT);
    	        		}
	    	        }                
	                catch (Exception e)
	                {
	                    e.printStackTrace();
	                }
	                finally
	                {
	                	
	                }
            	}            
    			Log.e(this.toString(),"thread terminate...");
            }
        }; 
       	
        m_write_thread =  new Thread()
        {
        	CMsgData data;
            public void run(){
            	while(true){
            		
            		if(!m_thread_run.get()){
            			break;
            		}
	            	try {
						data = write_queue.take();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
	            	
            		if(!m_thread_run.get()){
            			break;
            		}

					try {						
						
						if(data.m_port == 0 && m_remote_port !=0 && m_remote_address != null){
							SendPacket(data.m_data,m_remote_address,m_remote_port);
						} else {
							SendPacket(data.m_data,InetAddress.getByName(data.m_address),data.m_port);
						}
					} catch (UnknownHostException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}   
            	}
            }
		};

		m_thread_run.set(true);
		m_read_thread.start();
        m_write_thread.start();
       
    	return true;
    }
    
    public void UpdateStatus(int status) {
    	switch(status) {
    		case MSG_TIMEOUT:
    			break;
    		case MSG_READY:
    			break;
    	}
    }
    
    private void NoticeStatus(int status) {
    	Message msg = new Message();
        msg.what = status;
        m_read_handler.sendMessage(msg);    	
    }

	public void SendString(String str,String addr,int port)
	{
		if(!m_isReady)
			return;		
		
		CMsgData data = new CMsgData(addr,port,str);

        try {
        	write_queue.put(data);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void SendPacket(String str,InetAddress addr, int port)
	{
		if(!m_isReady)
			return;
		
		byte[] buf = str.getBytes();
	    DatagramPacket packet = 
	    	new DatagramPacket(buf, buf.length,addr,port);
		try {
			m_datagram_socket.send(packet);
		}
		catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	public void GetString(String str, String from, int port) {
		Log.e(this.toString(),"Get string:" + str + "from: " + from + ":" + port);
	}
	
	public void close() {
		try {			
			m_thread_run.set(false);			
			m_datagram_socket.close();
			
			m_read_thread.interrupt();	
			m_read_thread.join(3000);	
			CMsgData data = new CMsgData("",0,"");
			write_queue.put(data);
			m_write_thread.interrupt();	
			m_write_thread.join(3000);
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
    
    private String stringFromPacket(DatagramPacket packet) {
    	String str_tmp = new String(packet.getData(), 0, packet.getLength());  
        return str_tmp;      
    }       
}
